﻿using System;
using OverStore.Configuration.Validation;
using OverStore.Exceptions.Configuration;
using OverStore.Runtime.OverStoreContext;
using OverStore.Runtime.Storage;
using OverStore.Runtime.ObjectRepository.Mapping;
using OverStore.Runtime.ObjectRepository.IdentifierProvider;
using OverStore.Runtime.ObjectRepository.Reader;

namespace OverStore.Configuration.ObjectRepository.Reader
{
    /// <summary>
    /// Contains configuration parameters for creating parameterized readers.
    /// </summary>
    /// <typeparam name="TInstance">The type of the instance.</typeparam>
    /// <typeparam name="TIdentifier">The type of item identifier.</typeparam>
    /// <typeparam name="TParameter">The type of the parameter.</typeparam>
    public class ParameterizedReaderConfiguration<TInstance, TIdentifier, TParameter> : IComponentConfiguration<IReader<TInstance, TIdentifier>>,
        ISupportValidation
        where TParameter : class
        where TInstance : class
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ParameterizedReaderConfiguration&lt;TInstance, TIdentifier, TParameter&gt;"/> class.
        /// </summary>
        public ParameterizedReaderConfiguration()
        {
            UseResultSetForObjectConstruction = true;
        }

        /// <summary>
        /// Gets or sets the configuration for component uses for convert parameter
        /// instance to named set of values.
        /// </summary>
        public IComponentConfiguration<IMapper<TParameter>> ParameterMapper { get; set; }

        /// <summary>
        /// <para>Gets or sets true if reader should use data read
        /// from data stource for fill objec data; otherwise, reader should use object identifier for access instance 
        /// using <see cref="OverStore.Runtime.ObjectRepository.IEntityRegistry{TItem, TIdentifier}"/>.</para>
        /// </summary>
        public bool UseResultSetForObjectConstruction { get; set; }

        /// <summary>
        /// Gets or sets read command uses for read data from storage.
        /// </summary>
        public IComponentConfiguration<ICommand> ReadCommand { get; set; }

        /// <summary>
        /// Gets or sets the identifier provider uses for obtain identifiers from data.
        /// </summary>
        public IComponentConfiguration<IIdentifierProvider<TIdentifier>> IdentifierProvider { get; set; }

        /// <summary>
        /// Creates the component.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <returns></returns>
        public IReader<TInstance, TIdentifier> CreateComponent(IOverStoreContext context)
        {
            if (context == null)
                throw new ArgumentNullException("context");
            if (ParameterMapper == null)
                throw this.ConfigurationException("ParameterMapper is missing.");
            if (ReadCommand == null)
                throw this.ConfigurationException("ReadCommand is missing.");
            if (IdentifierProvider == null)
                throw this.ConfigurationException("Identifier Provider is missing.");

            return new ParameterizedReader<TInstance, TIdentifier, TParameter>(
                IdentifierProvider.CreateComponent(context),
                context.PersistenceServiceProvider,
                ParameterMapper.CreateComponent(context),
                ReadCommand.CreateComponent(context),
                context.DataStorage,
                context.Logger,
                UseResultSetForObjectConstruction);
        }

        /// <summary>
        /// Perform component validation. Method should validate only
        /// component configuration itself, not sub components.
        /// If configuration is valid, null result should returns.
        /// </summary>
        /// <returns>
        ///     <see cref="T:OverStore.Configuration.ValidationResults"/> describes errors found in component configuration.
        /// </returns>
        public ValidationResult Validate()
        {
            return new ValidationResult(this)
                .AddError(ParameterMapper == null, "Parameter mapper is missing.")
                .AddError(ReadCommand == null, "Read command is missing")
                .AddError(IdentifierProvider == null, "Identifier provider is required")
                .ValidateToNestedResult(ParameterMapper)
                .ValidateToNestedResult(ReadCommand);
        }
    }
}

